package org.zkoss.zktc.core.widget.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.zkoss.zktc.core.widget.Widget;
import org.zkoss.zktc.core.widget.WidgetDriver;

public class WidgetDriverImpl implements WidgetDriver {

	final WebDriver delegate;
	final boolean autoReattach;
	
	boolean clientJsLoaded;
	long timeout; // timeout for trip
	long zktimeout = 10000; // timeout for loading zk of a page


	public WidgetDriverImpl(WebDriver driver) {
		this(driver,10000, true);
	}

	public WidgetDriverImpl(WebDriver driver, long timeout, boolean autoReattach) {
		this.delegate = driver;
		this.timeout = timeout;
		this.autoReattach = autoReattach;
	}
	
	public void setZKTimeout(long zktimeout){
		this.zktimeout = zktimeout;
	}

	public long getTimeout() {
		return timeout;
	}
	
	public boolean isAutoReattach(){
		return autoReattach;
	}

	public WebDriver getDelegate() {
		return delegate;
	}

	public void navigate(String url) {
		clientJsLoaded = false;
		delegate.navigate().to(url);
	}

	public void loadClientJs() {
		loadClientJs(false);
	}
	public void loadClientJs(boolean force) {
		if (!force && clientJsLoaded)
			return;
		
		//wait for zk ready
		
		long t1 = System.currentTimeMillis();
		while (true) {
			boolean r = (Boolean)asRemoteDriver().executeScript("return window.zk?true:false");
			if(r) break;
			if (zktimeout<=0 || System.currentTimeMillis() - t1 >= zktimeout){
				throw new RuntimeException("zk is not ready after "+zktimeout+"s");
			}
		}
		
		
		clientJsLoaded = true;
		Object r = asRemoteDriver().executeScript("return window.zktc?true:false");
		if (Boolean.parseBoolean(r.toString())) {
			return;
		}
		String staticJs = WidgetScripts.instance().getStaticClientJs();
		asRemoteDriver().executeScript(staticJs);
	}

	private RemoteWebDriver asRemoteDriver() {
		if (delegate instanceof RemoteWebDriver) {
			return (RemoteWebDriver) delegate;
		}
		throw new UnsupportedOperationException(
				"cannot casting to RemoteWebDriver : " + delegate);
	}

	private String prepareJs(String fn) {
		loadClientJs();
		StringBuilder sb = new StringBuilder();
		sb.append("return zktc.").append(fn).append(".apply(zktc,arguments);");
		return sb.toString();
	}

	/* package */Object executeFns(String fn, Object... parms) {
		return asRemoteDriver().executeScript(prepareJs(fn), parms);
	}

	/* package */Object executeWidgetFns(String fn, WidgetImpl we,
			Object... parms) {
		Object[] ps = new Object[1 + (parms == null ? 0 : parms.length)];
		ps[0] = we.getGadgetId();
		if (parms != null) {
			System.arraycopy(parms, 0, ps, 1, parms.length);
		}
		return asRemoteDriver().executeScript(prepareJs("wgts."+fn), ps);
	}
	
	/* package */Object executeWidgetFns(String fn, WebElement we,
			Object... parms) {
		Object[] ps = new Object[1 + (parms == null ? 0 : parms.length)];
		ps[0] = we;
		if (parms != null) {
			System.arraycopy(parms, 0, ps, 1, parms.length);
		}
		return asRemoteDriver().executeScript(prepareJs("wgts."+fn), ps);
	}

	public long getTripId() {
		return (Long) executeFns("getTripId");
	}

	public void waitForTrip(int trip, long timeout) {
		waitForTrip(getTripId(), trip, timeout);
	}

	/* package */void waitForTrip(long tid, int trip, long timeout) {
		if(trip<=0) return;
		long t1 = System.currentTimeMillis();
		while (true) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
			}
			try{
				long tid2 = (Long) executeFns("getTripId");
				
				if (Math.abs(tid2 - tid) >= trip) {
					break;
				}
				if (timeout<=0 || System.currentTimeMillis() - t1 >= timeout)
					break;
			}catch(WebDriverException x){
				//page might reload will cause zktc is not defined, we ignore it here.
				break;
			}
		}
	}
	
//	public void waitForModule(String module, long timeout) {
//		long t1 = System.currentTimeMillis();
//		while (true) {
//			try {
//				Thread.sleep(100);
//			} catch (InterruptedException e) {
//			}
//			boolean r = (Boolean) executeFns("checkModule");
//			if (r || timeout<=0 || System.currentTimeMillis() - t1 >= timeout)
//				break;
//		}
//	}

	public WebElement findElement(String selector) {
		return findElement(selector, timeout);
	}

	public WebElement findElement(String selector, long timeout) {
		return findElement(null,selector,timeout);
	}
	WebElement findElement(WidgetImpl widget,String selector, long timeout) {
		long t1 = System.currentTimeMillis();
		WebElement r = null;
		if(widget!=null){
			selector = new StringBuilder().append("#").append(widget.getUuid()).append(" ").append(selector).toString();
		}
		while (r == null) {
			r = (WebElement) executeFns("findElement", selector);
			if (r == null) {
				if (timeout<=0 || System.currentTimeMillis() - t1 >= timeout)
					break;
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
				}
			}
		}
		return r;
	}

	public List<WebElement> findElements(String selector) {
		return findElements(selector,timeout);
	}
	public List<WebElement> findElements(String selector, long timeout) {
		return findElements(null,selector,timeout);
	}
	public List<WebElement> findElements(WidgetImpl widget,String selector, long timeout) {
		long t1 = System.currentTimeMillis();
		List r = null;
		if(widget!=null){
			selector = new StringBuilder().append("#").append(widget.getUuid()).append(" ").append(selector).toString();
		}
		while (r == null || r.size() == 0) {
			r = (List) executeFns("findElements", selector);
			if (r == null || r.size() == 0) {
				if (timeout<=0 || System.currentTimeMillis() - t1 >= timeout)
					break;
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
				}
			}
		}
		return r;
	}

	public Widget findWidget(String selector) {
		return findWidget(selector, false);
	}

	public Widget findWidget(String selector, long timeout) {
		return findWidget(selector, false, timeout);
	}

	public Widget findWidget(String selector, boolean strict) {
		return findWidget(selector,strict,timeout);
	}
	public Widget findWidget(String selector, boolean strict, long timeout) {
		return findWidget(null,selector,strict,timeout);
	}
	Widget findWidget(WidgetImpl widget,String selector, boolean strict, long timeout) {
		long t1 = System.currentTimeMillis();
		String wid = null;
		if(widget!=null){
			selector = new StringBuilder().append("#").append(widget.getUuid()).append(" ").append(selector).toString();
		}
		while (wid == null) {
			wid = (String)executeFns("findWidget", selector, Boolean.valueOf(strict));
			if (wid == null) {
				if (timeout<=0 || System.currentTimeMillis() - t1 >= timeout)
					break;
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
				}
			}
		}
		return toWidgetImpl(wid);
	}

	public List<Widget> findWidgets(String selector) {
		return findWidgets(selector, false);
	}

	public List<Widget> findWidgets(String selector, boolean strict) {
		return findWidgets(selector,strict,timeout);
	}
	public List<Widget> findWidgets(String selector, boolean strict, long timeout) {
		return findWidgets(null,selector,strict,timeout);
	}
	public List<Widget> findWidgets(WidgetImpl widget,String selector, boolean strict, long timeout) {
		long t1 = System.currentTimeMillis();
		List r = null;
		if(widget!=null){
			selector = new StringBuilder().append("#").append(widget.getUuid()).append(" ").append(selector).toString(); 
		}
		while (r == null || r.size() == 0) {
			r = (List)executeFns("findWidgets", selector, Boolean.valueOf(strict));
			if (r == null || r.size() == 0) {
				if (timeout<=0 || System.currentTimeMillis() - t1 >= timeout)
					break;
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
				}
			}
		}
		if(r==null || r.size()==0) return Collections.EMPTY_LIST;
		List<Widget> l = new ArrayList<Widget>();
		for (Object o : (List) r) {
			l.add(toWidgetImpl((String) o));
		}
		
		return l;
	}

	public void quit() {
		delegate.quit();
	}
	
	Widget toWidgetImpl(String gadgetId){
		if(gadgetId==null) return null;
		return new WidgetImpl(this,gadgetId,autoReattach);
	}


	public void waitForReady(long timeout) {
		long t1 = System.currentTimeMillis();
		while (true) {
			boolean r = (Boolean)asRemoteDriver().executeScript("return window.zk?true:false");
			if(r) break;
			if (zktimeout<=0 || System.currentTimeMillis() - t1 >= timeout){
				throw new RuntimeException("zk is not ready after "+zktimeout+"s");
			}
		}
		loadClientJs(true);
	}

	public void waitForReady() {
		this.waitForReady(zktimeout);
	}
	
	
}
